package com.remoter.api.extension.support;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.remoter.api.extension.annotation.Extension;
import com.remoter.api.extension.annotation.ExtensionAdaptive;
import com.remoter.api.extension.annotation.ExtensionAdaptiveParam;
import com.remoter.api.extension.annotation.ExtensionName;
import com.remoter.api.util.Proxy;
import com.remoter.api.util.StringUtil;


/**
 * 扩展点具体实现类
 * @author koko
 *
 */
public final class ExtensionLoader<T> {
	
	private static final ConcurrentHashMap<String,ExtensionLoader<?>> D_LOADERS = new ConcurrentHashMap<String,ExtensionLoader<?>>();
	private static final String D_SERVICES = "META-INF/services/";
	private static final String D_INTERNAL = "META-INF/internal/";
	
	private final Class<T> type;
	private final String name;
	private final ExtensionScope scope;
	private final boolean proxy;
	private final ConcurrentHashMap<String,Class<T>> S_CLASS = new ConcurrentHashMap<String,Class<T>>();
	private final ConcurrentHashMap<String,T> S_INSTANCE = new ConcurrentHashMap<String,T>();
	
	private ExtensionLoader(Class<T> type){
		if(null == type){
			throw new IllegalArgumentException("type is null");
		}
		if(!type.isInterface()){
			throw new IllegalArgumentException("type must interface");
		}
		Extension extension = type.getAnnotation(Extension.class);
		if(null == extension){
			throw new IllegalArgumentException("type has no @Extension");
		}
		this.type = type;
		this.name = StringUtil.isBlank(extension.value()) ? null : extension.value();
		this.scope = extension.scope();
		this.proxy = this.hasAdaptiveAnnotation();
		this.refresh();
	}
	
	private void refresh(){
		Map<String,Class<T>> classes = new HashMap<String,Class<T>>();
		this.loadFile(D_SERVICES,classes);
		this.loadFile(D_INTERNAL,classes);
		this.S_CLASS.putAll(classes);
	}
	
	private void loadFile(String path,Map<String,Class<T>> classes){
		try{
			String className = getClassName(this.type);
			String filePath = String.format("%s%s",path,className);
			Enumeration<URL> urls = null;
			ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
			if(null == classLoader){
				urls = ClassLoader.getSystemResources(filePath);
			}else{
				urls = classLoader.getResources(filePath);
			}
			if(null == urls){
				return;
			}
			while(urls.hasMoreElements()){
				URL url = urls.nextElement();
				try(
					InputStreamReader inputStreamReader = new InputStreamReader(url.openStream());
					BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
				){
					String line = null;
					String[] temp = null;
					String name = null;
					String clazz = null;
					int index = -1;
					while((line = bufferedReader.readLine()) != null){
						if(StringUtil.isBlank(line)){
							continue;
						}
						index = line.indexOf("=");
						if(index > 0){
							temp = line.split("=");
							name = temp[0].trim();
							clazz = temp[1].trim();
							if(StringUtil.isBlank(clazz)){
								continue;
							}
							if(StringUtil.isBlank(name)){
								name = clazz;
							}
						}else{
							name = clazz = line;
						}
						try{
							@SuppressWarnings("unchecked")
							Class<T> c = (Class<T>)Class.forName(clazz);
							if(this.type.isAssignableFrom(c)){
								ExtensionName metaName = c.getAnnotation(ExtensionName.class);
								if(null != metaName){
									name = metaName.value();
								}
								classes.put(name,c);
							}else{
								throw new IllegalArgumentException(clazz + " must implements " + this.type.getName());
							}
						}catch(Exception e){
							continue;
						}finally{
							temp = null;
							index = -1;
							name = null;
							clazz = null;
						}
					}
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public T getAdaptiveService(){
		if(!this.proxy){
			throw new UnsupportedOperationException("has no @ExtensionAdaptive");
		}
		if(this.scope == ExtensionScope.Prototype){
			return this.createProxy();
		}
		T result = S_INSTANCE.get("_PROXY");
		if(null == result){
			result = this.createProxy();
			S_INSTANCE.put("_PROXY",result);
		}
		return result;
	}
	
	public T getService(String name){
		if(StringUtil.isBlank(name)){
			name = this.name;
		}
		if(StringUtil.isBlank(name)){
			return null;
		}
		Class<T> clazz = S_CLASS.get(name);
		if(null == clazz){
			return null;
		}
		if(this.scope == ExtensionScope.Prototype){
			return this.createInstance(clazz);
		}
		T result = S_INSTANCE.get(name);
		if(null == result){
			result = this.createInstance(clazz);
			if(null == result){
				return null;
			}
			S_INSTANCE.put(name,result);
		}
		return result;
	}
	
	private T createInstance(Class<T> clazz){
		T result = null;
		try{
			result = clazz.newInstance();
		}catch(Throwable t){
			t.printStackTrace();
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private T createProxy(){
		return (T)Proxy.getProxy(this.type).newInstance(new ExtensionInvocationHandler(this.type));
	}
	
	private boolean hasAdaptiveAnnotation(){
		for(Method method : this.type.getDeclaredMethods()){
			if(null != method.getAnnotation(ExtensionAdaptive.class)){
				return true;
			}
		}
		return false;
	}
	
	private static String getClassName(Class<?> type){
		if(null == type){
			throw new IllegalArgumentException("type is null");
		}
		return type.getName();
	}
	
	private static class ExtensionInvocationHandler implements InvocationHandler{
		
		private Class<?> type;
		private Map<String,Annotation[][]> methodAnnotations = new HashMap<String,Annotation[][]>();
		
		public ExtensionInvocationHandler(Class<?> type){
			this.type = type;
			for(Method method : this.type.getDeclaredMethods()){
				if(null != method.getAnnotation(ExtensionAdaptive.class)){
					boolean has = false;
					Annotation[][] tempAnnotations = method.getParameterAnnotations();
					for(Annotation[] ans : tempAnnotations){
						for(Annotation an : ans){
							if(an.annotationType().equals(ExtensionAdaptiveParam.class)){
								has = true;
							}
						}
					}
					if(has){
						methodAnnotations.put(method.getName(),tempAnnotations);
					}
				}
			}
		}
		
		@Override
		public Object invoke(Object proxy,Method method,Object[] args)throws Throwable {
			String key = method.getName();
			if(!this.methodAnnotations.containsKey(key)){
				throw new IllegalArgumentException("has no @ExtensionAdaptive or @ExtensionAdaptiveParam");
			}
			Annotation[][] annotations = this.methodAnnotations.get(key);
			ExtensionLoader<?> loader = ExtensionLoader.getLoader(this.type);
			if(null == loader){
				throw new IllegalArgumentException("has no ExtensionLoader");
			}
			int index = 0;
			for(Annotation[] ans : annotations){
				if(null != ans && ans.length > 0 && args.length >= index){
					for(Annotation an : ans){
						if(an.annotationType().equals(ExtensionAdaptiveParam.class)){
							String name = null == args[index] ? null : args[index].toString();
							if(StringUtil.isNotBlank(name)){
								Object obj = loader.getService(name);
								if(null != obj){
									return method.invoke(obj,args);
								}
							}
						}
					}
				}
				index++;
			}
			throw new UnsupportedOperationException(this.type+"."+method.getName());
		}
	}
	
	@SuppressWarnings("unchecked")
	public static <E> ExtensionLoader<E> getLoader(Class<E> classType){
		String className = getClassName(classType);
		ExtensionLoader<E> extensionLoader = null;
		if(!D_LOADERS.containsKey(className)){
			D_LOADERS.putIfAbsent(className,new ExtensionLoader<E>(classType));
		}
		extensionLoader = (ExtensionLoader<E>)D_LOADERS.get(className);
		return extensionLoader;
	}
	
	public static <E> E getService(Class<E> classType,String name){
		ExtensionLoader<E> extensionLoader = getLoader(classType);
		if(null == extensionLoader){
			return null;
		}
		return extensionLoader.getService(name);
	}
	
	public static <E> E getAdaptiveService(Class<E> classType){
		ExtensionLoader<E> extensionLoader = getLoader(classType);
		if(null == extensionLoader){
			return null;
		}
		return extensionLoader.getAdaptiveService();
	}
	
	public static <E> E getService(Class<E> classType){
		return getService(classType,null);
	}
	
}